/* * Copyright 2012 * Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology * Technische Universität Darmstadt * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.tudarmstadt.ukp.clarin.webanno.constraints; import static de.tudarmstadt.ukp.clarin.webanno.api.ProjectService.PROJECT; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.Enumeration; import java.util.List; import java.util.Map.Entry; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import de.tudarmstadt.ukp.clarin.webanno.api.ProjectLifecycleAware; import de.tudarmstadt.ukp.clarin.webanno.constraints.grammar.ConstraintsGrammar; import de.tudarmstadt.ukp.clarin.webanno.constraints.grammar.ParseException; import de.tudarmstadt.ukp.clarin.webanno.constraints.grammar.syntaxtree.Parse; import de.tudarmstadt.ukp.clarin.webanno.constraints.model.ParsedConstraints; import de.tudarmstadt.ukp.clarin.webanno.constraints.model.Scope; import de.tudarmstadt.ukp.clarin.webanno.constraints.visitor.ParserVisitor; import de.tudarmstadt.ukp.clarin.webanno.model.ConstraintSet; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.support.ZipUtils; import de.tudarmstadt.ukp.clarin.webanno.support.logging.Logging; @Component(ConstraintsService.SERVICE_NAME) public class ConstraintsServiceImpl implements ConstraintsService, ProjectLifecycleAware { private static final String CONSTRAINTS = "/constraints/"; private final Logger log = LoggerFactory.getLogger(getClass()); @PersistenceContext private EntityManager entityManager; @Value(value = "${repository.path}") private File dir; public ConstraintsServiceImpl() { // Nothing to do } @Override @Transactional public List<ConstraintSet> listConstraintSets(Project aProject) { return entityManager .createQuery("FROM ConstraintSet WHERE project = :project ORDER BY name ASC ", ConstraintSet.class).setParameter("project", aProject).getResultList(); } @Override @Transactional public void createConstraintSet(ConstraintSet aSet) { entityManager.persist(aSet); try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Created constraints set [{}] in project [{}]({})", aSet.getName(), aSet.getProject().getName(), aSet.getProject().getId()); } } @Override @Transactional public void removeConstraintSet(ConstraintSet aSet) { entityManager.remove(entityManager.merge(aSet)); try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Removed constraints set [{}] in project [{}]({})", aSet.getName(), aSet.getProject().getName(), aSet.getProject().getId()); } } @Override public String readConstrainSet(ConstraintSet aSet) throws IOException { String constraintRulesPath = dir.getAbsolutePath() + PROJECT + aSet.getProject().getId() + CONSTRAINTS; String filename = aSet.getId() + ".txt"; String data = FileUtils.readFileToString(new File(constraintRulesPath, filename), "UTF-8"); try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Read constraints set [{}] in project [{}]({})", aSet.getName(), aSet.getProject().getName(), aSet.getProject().getId()); } return data; } @Override public void writeConstraintSet(ConstraintSet aSet, InputStream aContent) throws IOException { String constraintRulesPath = dir.getAbsolutePath() + PROJECT + aSet.getProject().getId() + CONSTRAINTS; String filename = aSet.getId() + ".txt"; FileUtils.forceMkdir(new File(constraintRulesPath)); FileUtils.copyInputStreamToFile(aContent, new File(constraintRulesPath, filename)); try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Saved constraints set [{}] in project [{}]({})", aSet.getName(), aSet.getProject().getName(), aSet.getProject().getId()); } } /** * Provides exporting constraints as a file. */ @Override public File exportConstraintAsFile(ConstraintSet aSet) { String constraintRulesPath = dir.getAbsolutePath() + PROJECT + aSet.getProject().getId() + CONSTRAINTS; String filename = aSet.getId() + ".txt"; File constraintsFile = new File(constraintRulesPath, filename); if (constraintsFile.exists()) { try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Exported constraints set [{}] from project [{}]({})", aSet.getName(), aSet.getProject().getName(), aSet.getProject().getId()); } return constraintsFile; } else { try (MDC.MDCCloseable closable = MDC.putCloseable(Logging.KEY_PROJECT_ID, String.valueOf(aSet.getProject().getId()))) { log.info("Unable to read constraints set file [{}] in project [{}]({})", filename, aSet.getProject().getName(), aSet.getProject().getId()); } return null; } } /** * Checks if there's a constraint set already with the name * @param constraintSetName The name of constraint set * @return true if exists */ @Override public boolean existConstraintSet(String constraintSetName, Project aProject){ try { entityManager.createQuery("FROM ConstraintSet WHERE project = :project" + " AND name = :name ", ConstraintSet.class) .setParameter("project", aProject). setParameter("name", constraintSetName) .getSingleResult(); return true; } catch (NoResultException ex) { return false; } } @Override public ParsedConstraints loadConstraints(Project aProject) throws IOException, ParseException { ParsedConstraints merged = null; for (ConstraintSet set : listConstraintSets(aProject)) { String script = readConstrainSet(set); ConstraintsGrammar parser = new ConstraintsGrammar(new StringReader(script)); Parse p = parser.Parse(); ParsedConstraints constraints = p.accept(new ParserVisitor()); if (merged == null) { merged = constraints; } else { // Merge imports for (Entry<String, String> e : constraints.getImports().entrySet()) { // Check if the value already points to some other feature in previous // constraint file(s). if (merged.getImports().containsKey(e.getKey()) && !e.getValue() .equalsIgnoreCase(merged.getImports().get(e.getKey()))) { // If detected, notify user with proper message and abort merging StringBuffer errorMessage = new StringBuffer(); errorMessage.append("Conflict detected in imports for key \""); errorMessage.append(e.getKey()); errorMessage.append("\", conflicting values are \""); errorMessage.append(e.getValue()); errorMessage.append("\" & \""); errorMessage.append(merged.getImports().get(e.getKey())); errorMessage.append( "\". Please contact Project Admin for correcting this. Constraints feature may not work."); errorMessage.append("\nAborting Constraint rules merge!"); throw new ParseException(errorMessage.toString()); } } merged.getImports().putAll(constraints.getImports()); // Merge scopes for (Scope scope : constraints.getScopes()) { Scope target = merged.getScopeByName(scope.getScopeName()); if (target == null) { // Scope does not exist yet merged.getScopes().add(scope); } else { // Scope already exists target.getRules().addAll(scope.getRules()); } } } } return merged; } @Override public void afterProjectCreate(Project aProject) throws Exception { // Nothing to do } @Override public void beforeProjectRemove(Project aProject) throws Exception { //Remove Constraints for (ConstraintSet set: listConstraintSets(aProject) ){ removeConstraintSet(set); } } @Override @Transactional public void onProjectImport(ZipFile aZip, de.tudarmstadt.ukp.clarin.webanno.export.model.Project aExportedProject, Project aProject) throws Exception { for (Enumeration zipEnumerate = aZip.entries(); zipEnumerate.hasMoreElements();) { ZipEntry entry = (ZipEntry) zipEnumerate.nextElement(); // Strip leading "/" that we had in ZIP files prior to 2.0.8 (bug #985) String entryName = ZipUtils.normalizeEntryName(entry); if (entryName.startsWith(CONSTRAINTS)) { String fileName = FilenameUtils.getName(entry.getName()); if(fileName.trim().isEmpty()){ continue; } ConstraintSet constraintSet = new ConstraintSet(); constraintSet.setProject(aProject); constraintSet.setName(fileName); createConstraintSet(constraintSet); writeConstraintSet(constraintSet, aZip.getInputStream(entry)); log.info("Imported constraint [" + fileName + "] for project [" + aProject.getName() + "] with id [" + aProject.getId() + "]"); } } } }