/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.dg.action;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.openflexo.docxparser.DocxFileParser;
import org.openflexo.docxparser.dto.api.IParsedDocx;
import org.openflexo.docxparser.dto.api.IParsedFlexoEPI;
import org.openflexo.docxparser.dto.api.IParsedFlexoObject;
import org.openflexo.docxparser.dto.api.IParsedHtml;
import org.openflexo.docxparser.dto.api.IParsedHtmlResource;
import org.openflexo.foundation.FlexoEditor;
import org.openflexo.foundation.FlexoException;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.foundation.IOFlexoException;
import org.openflexo.foundation.InvalidArgumentException;
import org.openflexo.foundation.action.FlexoActionType;
import org.openflexo.foundation.cg.CGObject;
import org.openflexo.foundation.cg.DGRepository;
import org.openflexo.foundation.cg.GeneratedDoc;
import org.openflexo.foundation.cg.action.AbstractGCAction;
import org.openflexo.foundation.ontology.EditionPatternInstance;
import org.openflexo.foundation.ontology.EditionPatternReference;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.rm.FlexoStorageResource;
import org.openflexo.foundation.rm.StorageResourceData;
import org.openflexo.foundation.toc.TOCEntry;
import org.openflexo.foundation.utils.FlexoModelObjectReference;
import org.openflexo.foundation.view.View;
import org.openflexo.foundation.view.ViewDefinition;
import org.openflexo.foundation.viewpoint.EditionPattern;
import org.openflexo.localization.FlexoLocalization;
import org.openflexo.toolbox.FileUtils;
import org.openflexo.toolbox.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
public class ReinjectDocx extends AbstractGCAction<ReinjectDocx, CGObject> {
private static final Logger logger = Logger.getLogger(ReinjectDocx.class.getPackage().getName());
private File docxToReinject;
private int numberOfDescriptionUpdated;
private int numberOfNameUpdated;
private int numberOfTocEntryTitleUpdated;
private int numberOfTocEntryContentUpdated;
private int numberOfObjectNotFound;
private int numberOfEPIUpdated;
private StringBuilder errorReport;
public static final FlexoActionType<ReinjectDocx, CGObject, CGObject> actionType = new FlexoActionType<ReinjectDocx, CGObject, CGObject>(
"reinject_docx", FlexoActionType.importMenu, FlexoActionType.defaultGroup, FlexoActionType.NORMAL_ACTION_TYPE) {
@Override
public boolean isEnabledForSelection(CGObject object, Vector<CGObject> globalSelection) {
return true;
}
@Override
public boolean isVisibleForSelection(CGObject object, Vector<CGObject> globalSelection) {
return true;
}
@Override
public ReinjectDocx makeNewAction(CGObject focusedObject, Vector<CGObject> globalSelection, FlexoEditor editor) {
return new ReinjectDocx(focusedObject, globalSelection, editor);
}
};
protected ReinjectDocx(CGObject focusedObject, Vector<CGObject> globalSelection, FlexoEditor editor) {
super(actionType, focusedObject, globalSelection, editor);
}
static {
FlexoModelObject.addActionForClass(ReinjectDocx.actionType, DGRepository.class);
FlexoModelObject.addActionForClass(ReinjectDocx.actionType, GeneratedDoc.class);
}
@Override
protected void doAction(Object context) throws FlexoException {
numberOfDescriptionUpdated = 0;
numberOfNameUpdated = 0;
numberOfTocEntryTitleUpdated = 0;
numberOfTocEntryContentUpdated = 0;
numberOfEPIUpdated = 0;
numberOfObjectNotFound = 0;
errorReport = new StringBuilder();
FileInputStream in;
try {
in = new FileInputStream(getDocxToReinject());
} catch (IOException e) {
throw new IOFlexoException(e);
}
try {
makeFlexoProgress(FlexoLocalization.localizedForKey("reinjecting_docx_file"), 2);
setProgress(FlexoLocalization.localizedForKey("parsing"));
File imagesDir = getEditor().getProject().getImportedImagesDir();
imagesDir = new File(imagesDir, "DocxReinject");
imagesDir.mkdirs();
String imageRelativePath = FileUtils.makeFilePathRelativeToDir(imagesDir, getEditor().getProject().getProjectDirectory());
DocxFileParser docxParser = new DocxFileParser(in, getAvailableCssClasses(), imageRelativePath);
IParsedDocx parsedDocx = docxParser.getParsedDocx();
setProgress(FlexoLocalization.localizedForKey("reinjecting_docx_file_in_model"));
resetSecondaryProgress(parsedDocx.getAllParsedFlexoDescriptions().size() + parsedDocx.getAllParsedFlexoNames().size()
+ parsedDocx.getAllParsedFlexoTitles().size() + parsedDocx.getAllParsedFlexoContents().size()
+ parsedDocx.getAllParsedEPIs().size() + 1);
// First load all unloaded resources
for (FlexoStorageResource<? extends StorageResourceData> resource : getFocusedObject().getProject().getStorageResources()) {
resource.getResourceData();// no need to mark as modified .setIsModified();
}
// Build a hash containing all flexoobject by "FlexoID_UserID"
Map<String, FlexoModelObject> allObjects = new HashMap<String, FlexoModelObject>();
List<FlexoModelObject> registeredObjects = new ArrayList<FlexoModelObject>();
registeredObjects.addAll(getFocusedObject().getProject().getAllRegisteredObjects()); // Create new to avoid concurrent
// modification exception
for (FlexoModelObject flexoObject : registeredObjects) {
allObjects.put(flexoObject.getSerializationIdentifier(), flexoObject);
}
Map<FlexoModelObject, String[]> duplicatedFlexoNames = reinjectContent(imagesDir, parsedDocx, allObjects);
handleDuplicatedNames(duplicatedFlexoNames);
reinjectEPI(parsedDocx);
} catch (InvalidFormatException e) {
throw new InvalidArgumentException(e.getMessage());
} catch (IOException e) {
throw new FlexoException("IO exception while trying to handle docx reinjection file resources", e);
} finally {
hideFlexoProgress();
try {
in.close();
} catch (IOException e) {
throw new IOFlexoException(e);
}
}
}
private void reinjectEPI(IParsedDocx parsedDocx) {
Multimap<EditionPatternInstance, IParsedFlexoEPI> epis = lookUpEditionPatternInstances(parsedDocx);
Multimap<EditionPatternInstance, IParsedFlexoEPI> episToReinject = removeConflictingParsedDocX(epis);
for (Entry<EditionPatternInstance, Collection<IParsedFlexoEPI>> e : episToReinject.asMap().entrySet()) {
EditionPatternInstance epi = e.getKey();
for (IParsedFlexoEPI parsedFlexoEPI : e.getValue()) {
setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_edition_pattern_value") + " "
+ parsedFlexoEPI.getValue());
boolean result = epi.setBindingValue(parsedFlexoEPI.getBindingPath(), parsedFlexoEPI.getValue());
if (result) {
numberOfEPIUpdated++;
}
}
}
}
public Multimap<EditionPatternInstance, IParsedFlexoEPI> removeConflictingParsedDocX(
Multimap<EditionPatternInstance, IParsedFlexoEPI> epis) {
Multimap<EditionPatternInstance, IParsedFlexoEPI> episToReinject = ArrayListMultimap.create();
Multimap<String, IParsedFlexoEPI> paths = ArrayListMultimap.create();
for (Entry<EditionPatternInstance, Collection<IParsedFlexoEPI>> e : epis.asMap().entrySet()) {
if (e.getValue().size() > 1) {
// There are multiple parsed DocX EPI for this EditionPatternInstance
// Let's see if it is for the same binding path
for (IParsedFlexoEPI epi : e.getValue()) {
paths.put(epi.getBindingPath(), epi);
}
for (Entry<String, Collection<IParsedFlexoEPI>> e1 : paths.asMap().entrySet()) {
boolean conflict = false;
if (e1.getValue().size() > 1) {
// There are multiple parsed DocX EPI for the same EPI and the same binding path
Object currentValue = e.getKey().evaluate(e1.getKey());
List<IParsedFlexoEPI> modified = new ArrayList<IParsedFlexoEPI>();
for (IParsedFlexoEPI epi : e1.getValue()) {
if (!epi.getValue().equals(currentValue)) {
modified.add(epi);
}
}
if (modified.size() > 1) {
// There is more than one parsed DocX EPI that has a different value than the current one
// Let's see if they are not all the same.
String value = modified.get(0).getValue();
for (int i = 1; i < modified.size(); i++) {
if (!value.equals(modified.get(i).getValue())) {
conflict = true;
errorReport.append("Conflicting values: ").append(value + " ").append(modified.get(i).getValue())
.append("\n");
break;
}
}
}
}
if (!conflict) {
episToReinject.putAll(e.getKey(), e1.getValue());
}
}
} else {
// There is a single parsed DocX EPI for this EditionPatternInstance
episToReinject.putAll(e.getKey(), e.getValue());
}
paths.clear();
}
return episToReinject;
}
public Multimap<EditionPatternInstance, IParsedFlexoEPI> lookUpEditionPatternInstances(IParsedDocx parsedDocx) {
Multimap<EditionPatternInstance, IParsedFlexoEPI> epis = ArrayListMultimap.create();
List<IParsedFlexoEPI> parsedEPIS = parsedDocx.getAllParsedEPIs();
FlexoProject project = getFocusedObject().getProject();
for (IParsedFlexoEPI epi : parsedEPIS) {
if (epi.getEditionPatternURI() == null) {
continue;
}
if (epi.getBindingPath() == null) {
continue;
}
if (epi.getEditionPatternInstanceID() == null || !epi.getEditionPatternInstanceID().matches("-?\\d+")) {
continue;
}
if (epi.getModelObjectReference() == null) {
continue;
}
FlexoModelObject object = new FlexoModelObjectReference<FlexoModelObject>(project, epi.getModelObjectReference())
.getObject(true);
long instanceID = Long.valueOf(epi.getEditionPatternInstanceID());
if (object != null) {
boolean found = false;
for (EditionPatternReference ref : object.getEditionPatternReferences()) {
if (ref.getEditionPattern() != null && ref.getEditionPattern().getURI().equals(epi.getEditionPatternURI())
&& ref.getInstanceId() == instanceID) {
if (ref.getEditionPatternInstance() != null) {
epis.put(ref.getEditionPatternInstance(), epi);
found = true;
break;
}
}
}
if (found) {
continue;
}
}
boolean found = false;
for (ViewDefinition vd : project.getShemaLibrary().getAllShemaList()) {
View view = vd.getShema();
if (view == null) {
continue;
}
EditionPattern pattern = null;
for (EditionPattern ep : view.getCalc().getEditionPatterns()) {
if (ep.getURI().equals(epi.getEditionPatternURI())) {
pattern = ep;
break;
}
}
if (pattern != null) {
for (EditionPatternInstance inst : vd.getShema().getEPInstances(pattern)) {
if (inst.getInstanceId() == instanceID) {
epis.put(inst, epi);
found = true;
break;
}
}
}
if (found) {
break;
}
}
if (found) {
continue;
}
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Could not find Edition pattern instance " + epi.getEditionPatternURI() + " "
+ epi.getEditionPatternInstanceID());
}
}
return epis;
}
public void handleDuplicatedNames(Map<FlexoModelObject, String[]> duplicatedFlexoNames) {
for (FlexoModelObject flexoObject : duplicatedFlexoNames.keySet()) {
try {
flexoObject.setName(duplicatedFlexoNames.get(flexoObject)[1]); // Try to set the new name
numberOfNameUpdated++;
} catch (Exception e) {
try {
// Ok cannot set the new name -> rollback to the old one
addToErrorReport("Cannot change name of object '" + duplicatedFlexoNames.get(flexoObject)[0] + "' to '"
+ duplicatedFlexoNames.get(flexoObject)[1] + "': the new name already exists");
flexoObject.setName(duplicatedFlexoNames.get(flexoObject)[0]);
} catch (Exception e1) { // Should not occur
logger.log(
Level.WARNING,
"Reinject docx: cannot roll back name for object '" + flexoObject.getName() + " ("
+ flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID() + ")' to '"
+ duplicatedFlexoNames.get(flexoObject)[0] + "'", e1);
addToErrorReport("Cannot rollback name of object '" + flexoObject.getName() + "' to '"
+ duplicatedFlexoNames.get(flexoObject)[0] + "': " + e1.getMessage());
}
}
}
}
public Map<FlexoModelObject, String[]> reinjectContent(File imagesDir, IParsedDocx parsedDocx, Map<String, FlexoModelObject> allObjects) {
Map<FlexoModelObject, String[]> duplicatedFlexoNames = new HashMap<FlexoModelObject, String[]>();
for (IParsedFlexoObject parsedFlexoObject : parsedDocx.getAllParsedFlexoObjects()) {
FlexoModelObject flexoObject = allObjects.get(FlexoModelObject.getSerializationIdentifier(parsedFlexoObject.getUserId(),
parsedFlexoObject.getFlexoId()));
if (flexoObject != null) {
if (parsedFlexoObject.getParsedFlexoDescription() != null) {
numberOfDescriptionUpdated++;
setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_description_in_model_for_object") + " "
+ flexoObject.getName());
for (String target : parsedFlexoObject.getParsedFlexoDescription().getAllTargets()) {
logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' description"
+ (target.length() > 0 ? " for target '" + target + "'" : ""));
IParsedHtml parsedHtml = parsedFlexoObject.getParsedFlexoDescription().getHtmlDescription(target);
if (target.length() == 0) {
flexoObject.setDescription(parsedHtml.getHtml());
} else {
flexoObject.setSpecificDescriptionsForKey(parsedHtml.getHtml(), target);
flexoObject.setHasSpecificDescriptions(true);
}
copyNeededResourcesFromParsedHtml(parsedHtml, imagesDir);
}
}
if (parsedFlexoObject.getParsedFlexoName() != null) {
setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_name_in_model_for_object") + " "
+ flexoObject.getName());
String oldName = flexoObject.getName();
String newName = parsedFlexoObject.getParsedFlexoName().getFlexoName();
if (newName != null
&& (oldName == null || !StringUtils.replaceBreakLinesBy(newName, " ").equals(
StringUtils.replaceBreakLinesBy(oldName, " ")))) {
try {
logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' name");
flexoObject.setName(newName);
numberOfNameUpdated++;
} catch (Exception e) {
// Can be a duplicate Exception -> try to add a unique suffix to allow name switch between two objects
// This suffix will be removed at the end of the process. Either the object name will be set to the newly
// set one if possible or it will be reset to its old name.
// Ideally, this should be performed only on duplicateException but there is too much different possible
// duplicate Exception type (with no common hierarchy).
try {
flexoObject.setName(newName + flexoObject.getUserIdentifier() + flexoObject.getFlexoID());
duplicatedFlexoNames.put(flexoObject, new String[] { oldName, newName });
} catch (Exception e1) {
logger.log(Level.WARNING, "Reinject docx: cannot set name for object '" + flexoObject.getName() + " ("
+ flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID() + ")' to '" + newName + "'", e1);
addToErrorReport("Cannot change name of object '" + oldName + "' to '" + newName + "': "
+ (e.getMessage() != null ? e.getMessage() : e.getClass()));
}
}
}
}
if (parsedFlexoObject.getParsedFlexoTitle() != null && flexoObject instanceof TOCEntry) {
TOCEntry tocEntry = (TOCEntry) flexoObject;
setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_title_in_model_for_tocentry") + " "
+ tocEntry.getTitle());
String oldTitle = tocEntry.getTitle();
String newTitle = parsedFlexoObject.getParsedFlexoTitle().getFlexoTitle();
if (newTitle != null
&& (oldTitle == null || !StringUtils.replaceBreakLinesBy(newTitle, " ").equals(
StringUtils.replaceBreakLinesBy(oldTitle, " ")))) {
logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' title");
tocEntry.setTitle(newTitle);
numberOfTocEntryTitleUpdated++;
}
}
if (parsedFlexoObject.getParsedFlexoContent() != null && flexoObject instanceof TOCEntry) {
TOCEntry tocEntry = (TOCEntry) flexoObject;
setSecondaryProgress(FlexoLocalization.localizedForKey("reinjecting_content_in_model_for_tocentry") + " "
+ tocEntry.getTitle());
logger.info("Updating object '" + flexoObject.getSerializationIdentifier() + "' content");
try {
tocEntry.setContent(parsedFlexoObject.getParsedFlexoContent().getFlexoContent().getHtml());
numberOfTocEntryContentUpdated++;
copyNeededResourcesFromParsedHtml(parsedFlexoObject.getParsedFlexoContent().getFlexoContent(), imagesDir);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Reinject docx: cannot set content for toc entry '" + tocEntry.getTitle() + " ("
+ flexoObject.getUserIdentifier() + "_" + flexoObject.getFlexoID() + ")'", e);
addToErrorReport("Cannot change content of toc entry '" + tocEntry.getTitle() + "': "
+ (e.getMessage() != null ? e.getMessage() : e.getClass()));
}
}
} else {
setSecondaryProgress(""); // Skip one, not found
logger.warning("ReinjectDocx: cannot find object with flexoid '" + parsedFlexoObject.getFlexoId() + "' and user id '"
+ parsedFlexoObject.getUserId() + "'");
numberOfObjectNotFound++;
}
}
return duplicatedFlexoNames;
}
private void copyNeededResourcesFromParsedHtml(IParsedHtml parsedHtml, File imagesDir) {
for (IParsedHtmlResource htmlResource : parsedHtml.getNeededResources()) { // Copy needed resources (images) in imported images
// directory
try {
File resourceFile = new File(imagesDir, htmlResource.getIdentifier());
resourceFile.createNewFile();
FileOutputStream outStream = new FileOutputStream(resourceFile);
outStream.write(htmlResource.getFile());
} catch (IOException e) {
logger.log(Level.WARNING, "Reinject docx: cannot write resource file with identifier '" + htmlResource.getIdentifier()
+ "'", e);
addToErrorReport("Cannot write resource file with identifier '" + htmlResource.getIdentifier() + "'");
}
}
}
private Set<String> getAvailableCssClasses() {
try {
Set<String> availableCssClasses = new HashSet<String>();
Enumeration<?> en = getEditor().getProject().getDocumentationCssResource().getStyleSheet().getStyleNames();
while (en.hasMoreElements()) {
String styleName = String.valueOf(en.nextElement());
if (styleName.startsWith(".") && styleName.trim().length() > 1) {
availableCssClasses.add(styleName.substring(1).trim());
}
}
return availableCssClasses;
} catch (MalformedURLException e) {
logger.log(Level.WARNING, "Cannot load css for reinject.", e);
return new HashSet<String>();
}
}
private void addToErrorReport(String msg) {
if (errorReport == null) {
errorReport = new StringBuilder();
} else if (errorReport.length() > 0) {
errorReport.append("\n");
}
errorReport.append("* " + msg);
}
public File getDocxToReinject() {
return docxToReinject;
}
public void setDocxToReinject(File docxToReinject) {
this.docxToReinject = docxToReinject;
}
public int getNumberOfDescriptionUpdated() {
return numberOfDescriptionUpdated;
}
public int getNumberOfNameUpdated() {
return numberOfNameUpdated;
}
public int getNumberOfTocEntryTitleUpdated() {
return numberOfTocEntryTitleUpdated;
}
public int getNumberOfTocEntryContentUpdated() {
return numberOfTocEntryContentUpdated;
}
public int getNumberOfObjectNotFound() {
return numberOfObjectNotFound;
}
public int getNumberOfEPIUpdated() {
return numberOfEPIUpdated;
}
public String getErrorReport() {
return errorReport != null ? errorReport.toString() : "";
}
public boolean hasError() {
return errorReport != null && errorReport.length() > 0;
}
}