/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
* <p>
* Initial code contributed and copyrighted by<br>
* JGS goodsolutions GmbH, http://www.goodsolutions.ch
* <p>
*/
package org.olat.core.id.context;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.resource.OresHelper;
import org.olat.core.util.servlets.URLEncoder;
/**
* Description:<br>
*
* <P>
* Initial Date: 14.06.2006 <br>
*
* @author Felix Jost
*/
public class BusinessControlFactory {
private static final OLog log = Tracing.createLoggerFor(BusinessControlFactory.class);
private static final BusinessControlFactory INSTANCE = new BusinessControlFactory();
final BusinessControl EMPTY; // for performance
private static final Pattern PAT_CE = Pattern.compile("\\[([^\\]]*)\\]");
private static final DateFormat ceDateFormat = new SimpleDateFormat("yyyyMMdd");
private BusinessControlFactory() {
// singleton
EMPTY = new BusinessControl() {
public String toString() {
return "[EMPTY(cnt:0, curPos:1) ]";
}
public String getAsString() {
return "";
}
@Override
public List<ContextEntry> getEntries() {
return Collections.<ContextEntry>emptyList();
}
@Override
public List<ContextEntry> getEntriesDownTheControls() {
return Collections.<ContextEntry>emptyList();
}
public ContextEntry popLauncherContextEntry() {
return null;
}
public void dropLauncherEntries() {
throw new AssertException("dropping all entries, even though EMPTY");
}
public boolean hasContextEntry() {
return false;
}
@Override
public ContextEntry getCurrentContextEntry() {
return null;
}
public void setCurrentContextEntry(ContextEntry cw) {
throw new AssertException("wrong call");
}
};
}
public static BusinessControlFactory getInstance() {
return INSTANCE;
}
/**
*
* to be used when a new window is opened (see references to this method as an example)
*
* @param contextEntry
* @param windowWControl
* @param businessWControl
* @return
*/
public WindowControl createBusinessWindowControl(final ContextEntry contextEntry, WindowControl windowWControl, WindowControl businessWControl) {
BusinessControl origBC = businessWControl.getBusinessControl();
BusinessControl bc;
if (contextEntry != null) {
bc = new StackedBusinessControl(contextEntry, origBC);
} else {
// pass through
bc = origBC;
}
WindowControl wc = new StackedBusinessWindowControl(windowWControl, bc);
return wc;
}
public BusinessControl createBusinessControl(ContextEntry ce, BusinessControl origBC) {
if (origBC == null) {
origBC = EMPTY;
}
BusinessControl bc = new StackedBusinessControl(ce, origBC);
return bc;
}
/**
* to be used when a new controller (but not in a new window) is opened (a controller with a contextual business id, that is, the
* parent opening the controller provides a id = how it will "call" the newly generated controller). it needs to be able to reopen the same controller
* upon e.g. request by the search engine when a user clicks on a search result.
* @param contextEntry
* @param origWControl
* @return
*/
public WindowControl createBusinessWindowControl(final ContextEntry contextEntry, WindowControl origWControl) {
BusinessControl origBC = origWControl.getBusinessControl();
BusinessControl bc = new StackedBusinessControl(contextEntry, origBC);
WindowControl wc = new StackedBusinessWindowControl(origWControl, bc);
return wc;
}
/**
* The method check for duplicate entries!!!
* @param ores
* @param wControl
* @return
*/
public WindowControl createBusinessWindowControl(final OLATResourceable ores, StateEntry state, WindowControl wControl) {
WindowControl bwControl;
ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(ores);
if(ce.equals(wControl.getBusinessControl().getCurrentContextEntry())) {
bwControl = wControl;
wControl.getBusinessControl().getCurrentContextEntry().setTransientState(state);
} else {
bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl);
ce.setTransientState(state);
}
return bwControl;
}
/**
* The method check for duplicate entries!!!
* @param ores
* @param wControl
* @return
*/
public WindowControl createBusinessWindowControl(UserRequest ureq, final OLATResourceable ores, StateEntry state,
WindowControl wControl, boolean addToHistory) {
WindowControl bwControl = createBusinessWindowControl(ores, state, wControl);
if(addToHistory) {
ureq.getUserSession().addToHistory(ureq, bwControl.getBusinessControl());
}
return bwControl;
}
public void addToHistory(UserRequest ureq, WindowControl wControl) {
if(wControl == null || wControl.getBusinessControl() == null) return;
ureq.getUserSession().addToHistory(ureq, wControl.getBusinessControl());
}
public void addToHistory(UserRequest ureq, HistoryPoint historyPoint) {
ureq.getUserSession().addToHistory(ureq, historyPoint);
}
public void removeFromHistory(UserRequest ureq, WindowControl wControl) {
if(wControl == null || wControl.getBusinessControl() == null) return;
ureq.getUserSession().removeFromHistory(wControl.getBusinessControl());
}
public WindowControl createBusinessWindowControl(BusinessControl businessControl, WindowControl origWControl) {
WindowControl wc = new StackedBusinessWindowControl(origWControl, businessControl);
return wc;
}
public WindowControl createBusinessWindowControl(WindowControl origWControl, OLATResourceable... ores) {
List<ContextEntry> ces;
if(ores != null && ores.length > 0) {
ces = new ArrayList<ContextEntry>(ores.length);
for(OLATResourceable o:ores) {
ces.add(createContextEntry(o));
}
} else {
ces = Collections.emptyList();
}
BusinessControl bc = createFromContextEntries(ces);
return createBusinessWindowControl(bc, origWControl);
}
public BusinessControl getEmptyBusinessControl() {
// immutable, so therefore we can reuse it
return EMPTY;
}
public ContextEntry createContextEntry(OLATResourceable ores) {
return new MyContextEntry(ores);
}
public ContextEntry createContextEntry(Identity identity) {
OLATResourceable ores = OresHelper.createOLATResourceableInstance(Identity.class, identity.getKey());
return new MyContextEntry(ores);
}
public String getAsString(BusinessControl bc) {
return bc.getAsString();
}
public BusinessControl createFromString(String businessControlString) {
final List<ContextEntry> ces = createCEListFromString(businessControlString);
if (ces.isEmpty() || ces.get(0) ==null) {
log.warn("OLAT-4103, OLAT-4047, empty or invalid business controll string. list is empty. string is "+businessControlString, new Exception("stacktrace"));
}
return createFromContextEntries(ces);
}
public BusinessControl createFromPoint(HistoryPoint point) {
final List<ContextEntry> ces = point.getEntries();
if (ces.isEmpty() || ces.get(0) == null) {
log.warn("OLAT-4103, OLAT-4047, empty or invalid business controll string. list is empty. string is " + point.getBusinessPath(), new Exception("stacktrace"));
}
return createFromContextEntries(ces);
}
public List<ContextEntry> cloneContextEntries(final List<ContextEntry> ces) {
final List<ContextEntry> clones = new ArrayList<ContextEntry>(ces.size());
for(ContextEntry ce:ces) {
OLATResourceable clone = OresHelper.clone(ce.getOLATResourceable());
clones.add(new MyContextEntry(clone));
}
return clones;
}
public BusinessControl createFromContextEntries(final List<ContextEntry> ces) {
ContextEntry rootEntry = null;
if (ces.isEmpty() || ((rootEntry = ces.get(0))==null)) {
log.warn("OLAT-4103, OLAT-4047, empty or invalid business controll string. list is empty.", new Exception("stacktrace"));
}
//Root businessControl with RootContextEntry which must be defined (i.e. not null)
BusinessControl bc = new StackedBusinessControl(rootEntry, null) {
@Override
public ContextEntry popLauncherContextEntry() {
return popInternalLaucherContextEntry();
}
@Override
ContextEntry popInternalLaucherContextEntry(){
if (ces.size() == 0) return null;
ContextEntry ce = ces.remove(0);
return ce;
}
@Override
public List<ContextEntry> getEntriesDownTheControls() {
List<ContextEntry> allEntries = new ArrayList<>();
List<ContextEntry> entries = super.getEntries();
if(entries != null) {
allEntries.addAll(entries);
}
if(ces != null) {
allEntries.addAll(ces);
}
return allEntries;
}
@Override
public void dropLauncherEntries() {
ces.clear();
}
@Override
public boolean hasContextEntry() {
return ces.size() > 0;
}
};
return bc;
}
public List<ContextEntry> createCEListFromString(OLATResourceable... resources) {
List<ContextEntry> entries = new ArrayList<ContextEntry>();
if(resources != null && resources.length > 0) {
for(OLATResourceable resource:resources) {
entries.add(createContextEntry(resource));
}
}
return entries;
}
public List<ContextEntry> createCEListFromResourceable(OLATResourceable resource, StateEntry stateEntry) {
List<ContextEntry> entries = new ArrayList<ContextEntry>();
ContextEntry entry = createContextEntry(resource);
entry.setTransientState(stateEntry);
entries.add(entry);
return entries;
}
/**
* helloworld will be an entry helloworld:0
* @param resourceType
* @return
*/
public List<ContextEntry> createCEListFromResourceType(String resourceType) {
List<ContextEntry> entries = new ArrayList<ContextEntry>(3);
if(StringHelper.containsNonWhitespace(resourceType)) {
OLATResourceable ores = OresHelper.createOLATResourceableInstanceWithoutCheck(resourceType, 0l);
ContextEntry entry = createContextEntry(ores);
entries.add(entry);
}
return entries;
}
/**
* e.g. [repo:123][CourseNode:345][folder][path=/sdfsd/sdfd:0]
* @param businessControlString
* @return
*/
public List<ContextEntry> createCEListFromString(String businessControlString) {
List<ContextEntry> entries = new ArrayList<ContextEntry>();
if(!StringHelper.containsNonWhitespace(businessControlString)) {
return entries;
}
Matcher m = PAT_CE.matcher(businessControlString);
while (m.find()) {
String ces = m.group(1);
int pos = ces.lastIndexOf(':');
OLATResourceable ores;
if(pos == -1) {
if(ces.startsWith("path=")) {
ces = ces.replace("|", "/");
}
ores = OresHelper.createOLATResourceableTypeWithoutCheck(ces);
} else {
String type = ces.substring(0, pos);
String keyS = ces.substring(pos+1);
if(type.startsWith("path=")) {
ces = type.replace("|", "/");
}
try {
Long key = Long.parseLong(keyS);
ores = OresHelper.createOLATResourceableInstanceWithoutCheck(type, key);
} catch (NumberFormatException e) {
log.warn("Cannot parse business path:" + businessControlString, e);
return entries;//return what we decoded
}
}
ContextEntry ce = createContextEntry(ores);
entries.add(ce);
}
return entries;
}
/**
* Return an URL in the form of http://www.olat.org:80/olat/url/RepsoitoryEntry/49358
* @param bc
* @param normalize If true, prevent duplicate entry (it can happen)
* @return
*/
public String getAsURIString(BusinessControl bc, boolean normalize) {
String businessPath = bc.getAsString();
List<ContextEntry> ceList = createCEListFromString(businessPath);
String restUrl = getAsURIString(ceList, normalize);
return restUrl;
}
/**
* Return an URL in the form of http://www.olat.org:80/olat/url/RepsoitoryEntry/49358
* @param ceList
* @param normalize If true, prevent duplicate entries (it can happen)
* @return
*/
public String getAsURIString(List<ContextEntry> ceList, boolean normalize) {
if(ceList == null || ceList.isEmpty()) return "";
StringBuilder retVal = new StringBuilder();
retVal.append(Settings.getServerContextPathURI())
.append("/url/");
return appendToURIString(retVal, ceList, normalize);
}
public String getAsAuthURIString(List<ContextEntry> ceList, boolean normalize) {
StringBuilder retVal = new StringBuilder();
retVal.append(Settings.getServerContextPathURI())
.append("/auth/");
if(ceList == null || ceList.isEmpty()) {
return retVal.toString();
}
return appendToURIString(retVal, ceList, normalize);
}
public String getAsRestPart(List<ContextEntry> ceList, boolean normalize) {
StringBuilder retVal = new StringBuilder();
if(ceList == null || ceList.isEmpty()) {
return retVal.toString();
}
return appendToURIString(retVal, ceList, normalize);
}
private String appendToURIString(StringBuilder retVal, List<ContextEntry> ceList, boolean normalize) {
String lastEntryString = null;
for (ContextEntry contextEntry : ceList) {
String ceStr = contextEntry != null ? contextEntry.toString() : "NULL_ENTRY";
if(normalize) {
if(lastEntryString == null){
lastEntryString = ceStr;
} else if (lastEntryString.equals(ceStr)) {
continue;
}
}
if(ceStr.startsWith("[path")) {
//the %2F make a problem on browsers.
//make the change only for path which is generally used
//TODO: find a better method or a better separator as |
ceStr = ceStr.replace("%2F", "~~");
}
ceStr = ceStr.replace(':', '/');
ceStr = ceStr.replaceFirst("\\]", "/");
ceStr= ceStr.replaceFirst("\\[", "");
retVal.append(ceStr);
}
return retVal.substring(0, retVal.length()-1);
}
/**
* Return the standard format for date: [date=20120223:0]
* @param date
* @return
*/
public String getContextEntryStringForDate(Date date) {
StringBuilder sb = new StringBuilder("[date=");
synchronized(ceDateFormat) {//DateFormat isn't thread safe but costly to create, we reuse it
sb.append(ceDateFormat.format(date));
}
sb.append(":0]");
return sb.toString();
}
public Date getDateFromContextEntry(ContextEntry entry) {
String dateEntry = entry.getOLATResourceable().getResourceableTypeName();
Date date = null;
if(dateEntry.startsWith("date=")) {
try {
int sepIndex = dateEntry.indexOf(':');
int lastIndex = (sepIndex > 0 ? sepIndex : dateEntry.length());
if(lastIndex > 0) {
String dateStr = dateEntry.substring("date=".length(), lastIndex);
synchronized(ceDateFormat) {//DateFormat isn't thread safe but costly to create, we reuse it
date = ceDateFormat.parse(dateStr);
}
}
} catch (ParseException e) {
log.warn("Error parsing the date after activate: " + dateEntry, e);
}
}
return date;
}
public String getPath(ContextEntry entry) {
String path = entry.getOLATResourceable().getResourceableTypeName();
path = path.endsWith(":0") ? path.substring(0, path.length() - 2) : path;
path = path.startsWith("path=") ? path.substring(5, path.length()) : path;
return path;
}
public String getBusinessPathAsURIFromCEList(List<ContextEntry> ceList){
if(ceList == null || ceList.isEmpty()) return "";
StringBuilder retVal = new StringBuilder();
for (ContextEntry contextEntry : ceList) {
String ceStr = contextEntry != null ? contextEntry.toString() : "NULL_ENTRY";
if(ceStr.startsWith("[path")) {
//the %2F make a problem on browsers.
//make the change only for path which is generally used
ceStr = ceStr.replace("%2F", "~~");
}
ceStr = ceStr.replace(':', '/');
ceStr = ceStr.replaceFirst("\\]", "/");
ceStr= ceStr.replaceFirst("\\[", "");
retVal.append(ceStr);
}
return retVal.substring(0, retVal.length()-1);
}
public String getURLFromBusinessPathString(String bPathString){
if(!StringHelper.containsNonWhitespace(bPathString)) {
return null;
}
try {
BusinessControlFactory bCF = BusinessControlFactory.getInstance();
List<ContextEntry> ceList = bCF.createCEListFromString(bPathString);
String busPath = getBusinessPathAsURIFromCEList(ceList);
return Settings.getServerContextPathURI()+"/url/"+busPath;
} catch(Exception e) {
log.error("Error with business path: " + bPathString, e);
return null;
}
}
public String getRelativeURLFromBusinessPathString(String bPathString){
if(!StringHelper.containsNonWhitespace(bPathString)) {
return null;
}
try {
List<ContextEntry> ceList = createCEListFromString(bPathString);
String busPath = getBusinessPathAsURIFromCEList(ceList);
return WebappHelper.getServletContextPath() + "/url/" + busPath;
} catch(Exception e) {
log.error("Error with business path: " + bPathString, e);
return null;
}
}
public String formatFromURI(String restPart) {
try {
restPart = URLDecoder.decode(restPart, "UTF8");
} catch (UnsupportedEncodingException e) {
log.error("Unsupported encoding", e);
}
String[] split = restPart.split("/");
if (split.length % 2 != 0) {
return null;
}
return formatFromSplittedURI(split);
}
public String formatFromSplittedURI(String[] split) {
StringBuilder businessPath = new StringBuilder(64);
for (int i = 0; i < split.length; i=i+2) {
String key = split[i];
if(key != null && key.startsWith("path=")) {
key = key.replace("~~", "/");
}
String value = split[i+1];
businessPath.append("[").append(key).append(":").append(value).append("]");
}
return businessPath.toString();
}
}
class MyContextEntry implements ContextEntry, Serializable {
private static final long serialVersionUID = 949522581806327579L;
private OLATResourceable olatResourceable;
private StateEntry state;
MyContextEntry(OLATResourceable ores) {
this.olatResourceable = ores;
}
/**
* @return Returns the olatResourceable.
*/
public OLATResourceable getOLATResourceable() {
return olatResourceable;
}
@Override
public void upgradeOLATResourceable(OLATResourceable ores) {
olatResourceable = ores;
}
@Override
public StateEntry getTransientState() {
return state;
}
@Override
public void setTransientState(StateEntry state) {
this.state = state;
}
@Override
public ContextEntry clone() {
MyContextEntry entry = new MyContextEntry(olatResourceable);
if(state != null) {
entry.state = state.clone();
}
return entry;
}
@Override
public String toString(){
URLEncoder urlE = new URLEncoder();
String resource =urlE.encode(this.olatResourceable.getResourceableTypeName());
return "["+resource+":"+this.olatResourceable.getResourceableId()+"]";
}
@Override
public int hashCode() {
return (olatResourceable==null) ? super.hashCode() : olatResourceable.hashCode();
}
@Override
public boolean equals(Object obj) {
if (olatResourceable==null) {
return super.equals(obj);
} else if (obj instanceof MyContextEntry) {
MyContextEntry mce = (MyContextEntry)obj;
// safe comparison including null value checks
Long myResId = olatResourceable.getResourceableId();
Long itsResId = mce.olatResourceable.getResourceableId();
if (myResId==null && itsResId!=null) return false;
if (myResId!=null && itsResId==null) return false;
if (myResId!=null && itsResId!=null) {
if (!myResId.equals(itsResId)) return false;
}
String myResName = olatResourceable.getResourceableTypeName();
String itsResName = mce.olatResourceable.getResourceableTypeName();
if (myResName==null && itsResName!=null) return false;
if (myResName!=null && itsResName==null) return false;
if (myResName!=null && itsResName!=null) {
if (!myResName.equals(itsResName)) {
return false;
}
}
if(state == null && mce.state == null) {
return true;
} else if (state != null && state.equals(mce.state)) {
return true;
}
return false;
} else {
return super.equals(obj);
}
}
}